package cn.edu.neu.lab603.cloudeval.strategy.policy

import cn.edu.neu.lab603.cloudeval.entity.{DataCenter, PhysicalMachine => Host, VirtualMachine => VM}
import cn.edu.neu.lab603.math.createLinearRegression
import org.apache.commons.math3.linear.Array2DRowRealMatrix

import scala.collection.mutable.ListBuffer

/** 实现最大关联度的虚拟机选择策略。
  *
  * @param fallback 失败时启用的选择策略
  * @author yfwz100
  */
class VMSelectionPolicyMaximumCorrelation(val fallback: VMSelectionPolicy) extends VMSelectionPolicy {

  import MigrationDef._

  def this() = this(new VMSelectionPolicyMinimumMigrationTime)

  override def getVMFromMigratedHost(host: Host)(implicit dataCenter: DataCenter): Iterable[VM] = {
    val vms = host.getMigratableVMs
    if (vms.nonEmpty) {
      val metrics: Seq[Double] = try {
        getCorrelationCoefficients(getUtilizationMatrix(vms))
      } catch {
        case _: IllegalArgumentException => return fallback.getVMFromMigratedHost(host)
      }
      var maxIdx = 0
      for (i <- metrics.indices) {
        if (metrics(i) > metrics(maxIdx)) {
          maxIdx = i
        }
      }
      Seq(vms(maxIdx))
    } else {
      Seq.empty
    }
  }

  protected def getUtilizationMatrix(vmList: Seq[VM]): Array[Array[Double]] = {
    val n: Int = vmList.size
    val emptyListBuffer = ListBuffer.empty
    val m = vmList.map(_.getUtilizationHistory.getOrElse("mips", emptyListBuffer).size).min
    val utilization: Array[Array[Double]] = Array.ofDim(n, m)
    for (i <- 0 until n) {
      val vmUtilization = vmList(i).getUtilizationHistory.getOrElse("mips", emptyListBuffer)
      for (j <- 0 until m) {
        utilization(i)(j) = vmUtilization(j)
      }
    }
    utilization
  }

  /** Gets the correlation coefficients.
    *
    * @param data the data
    * @return the correlation coefficients
    */
  protected def getCorrelationCoefficients(data: Array[Array[Double]]): Seq[Double] = {
    val n: Int = data.length
    val m: Int = data(0).length
    val correlationCoefficients = new ListBuffer[Double]
    for (i <- 0 until n) {
      val x: Array[Array[Double]] = Array.ofDim(n - 1, m)
      var k: Int = 0
      for (j <- 0 until n) {
        if (j != i) {
          x(k) = data(j)
          k += 1
        }
      }
      // Transpose the matrix so that it fits the linear model
      val xT: Array[Array[Double]] = new Array2DRowRealMatrix(x).transpose.getData
      // RSquare is the "coefficient of determination"
      correlationCoefficients += createLinearRegression(xT, data(i)).calculateRSquared
    }
    correlationCoefficients
  }

  /** Gets the min CPU utilization percentage history size among a list of VMs.
    *
    * @param vmList the VM list
    * @return the min CPU utilization percentage history size of the VM list
    */
  protected def getMinUtilizationHistorySize(vmList: Seq[VM]): Int = {
    var minSize: Int = Integer.MAX_VALUE
    for (vm <- vmList) {
      val size: Int = vm.getUtilizationHistory.getOrElse("mips", ListBuffer.empty).size
      if (size < minSize) minSize = size
    }
    minSize
  }

  override def toString: String = "MC"
}
